#include "calculator.h"
#include <QStack>
#include <QVariant>
#include <QVector>
#include <string>
#include <QDebug>

using std::string;

Calculator::Calculator()
{

}

double Calculator::exec(const QString &expr)
{
    qDebug() << "expressioin: " << expr;
    clearError();
    QList<QVariant> expression = parseExpression(expr);
    if (expression.empty()) {
        return 0.0;
    }

    QStack<double> nums;
    for(auto& val : expression) {
        if (val.type() == QVariant::Double) {
            nums.push(val.value<double>());
        } else {
            // operator
            auto op = val.toString();
            if (nums.size() >= 2) {
                auto b = nums.top();
                nums.pop();
                auto a = nums.top();
                nums.pop();
                nums.push(calc(a, b, op));
                if (isError()) {
                    return 0.0;
                }
            }
        }
    }

    return nums.top();
}

bool Calculator::isError() const
{
    return mError != Error::ERR_OK;
}

Calculator::Error Calculator::errorCode() const
{
    return mError;
}

void Calculator::clearError()
{
    setError(Error::ERR_OK);
}

double Calculator::strToDouble(const char *p, int& pos)
{
    char* next = nullptr;
    double number = std::strtod(p + pos, &next);
    if (number == 0 && next == p + pos) {
        setError(Error::ERR_INVALID_EXPR);
    }
    pos = next - p;

    return number;
}

QList<QVariant> Calculator::parseExpression(const QString &expr)
{
    QList<QVariant> tokens;
    QStack<QString> ops;
    auto str = expr.toStdString();
    int exprLen = str.size();
    const char* p = str.c_str();
    int i = 0;
    bool hasNumber = false;
    bool isNegative = false;
    while(i < exprLen) {
        auto ch = str[i];
        if (std::isdigit(ch)) {
            double number = strToDouble(p, i);
            if (isError()) {
                return QList<QVariant>{};
            }
            if (isNegative) {
                isNegative = false;
                number = -number;
            }
            tokens.push_back(number);
            hasNumber = true;
        } else if (ch == '+' || ch == '-') {
            if (ch == '-') {
                if (i + 1 < exprLen && std::isdigit(str[i+1]) && !hasNumber) {
                    isNegative = true;
                    ++i;
                    continue;
                }
            }

            hasNumber = false;
            while(!ops.empty() && ops.top() != "(") {
                tokens.push_back(ops.top());
                ops.pop();
            }
            ops.push(QString(ch));
            ++i;
        } else if (ch == 'x' || ch == '/') {
            hasNumber = false;
            while(!ops.empty() && (ops.top() == "x" || ops.top() == "/")) {
                tokens.push_back(ops.top());
                ops.pop();
            }
            ops.push(QString(ch));
            ++i;
        } else if (ch == '(') {
            hasNumber = false;
            ops.push(QString(ch));
            ++i;
        } else if (ch == ')') {
            hasNumber = false;
            while(!ops.empty() && ops.top() != "(") {
                tokens.push_back(ops.top());
                ops.pop();
            }
            if (!ops.empty()) {
                ops.pop(); // top is "("
            }
            ++i;
        } else {
            setError(Error::ERR_INVALID_EXPR);
            return QList<QVariant>{};
        }
    }

    while(!ops.empty()) {
        tokens.push_back(ops.top());
        ops.pop();
    }

    return tokens;
}

void Calculator::setError(Error error)
{
    mError = error;
}

double Calculator::calc(double num1, double num2, const QString &op)
{
    if (op == "+") {
        return add(num1, num2);
    } else if (op == "-") {
        return sub(num1, num2);
    } else if (op == "x") {
        return mul(num1, num2);
    } else if (op == "/") {
        return div(num1, num2);
    } else {
        setError(Error::ERR_INVALID_EXPR);
        return 0.0;
    }
}


double Calculator::add(double num1, double num2)
{
    return num1 + num2;
}

double Calculator::sub(double num1, double num2)
{
    return num1 - num2;
}

double Calculator::mul(double num1, double num2)
{
    return num1 * num2;
}

double Calculator::div(double num1, double num2)
{
    if (num2 == 0) {
        setError(Error::ERR_DIVIDE_ZERO);
        return 0;
    }
    return num1 / num2;
}
